Namespace DirectoryHelper

    <AttributeUsage(AttributeTargets.Property, AllowMultiple:=False, Inherited:=True)> _
    Public Class ExtendedInfoAttribute
        Inherits Attribute

        Public Sub New(ByVal mode As String, ByVal name As String)
            MyBase.New()
            _AccessMode = mode
        End Sub

        Private _AccessMode As String
        Public Overridable ReadOnly Property AccessMode() As String
            Get
                Return _AccessMode
            End Get
        End Property

        Private _FriendlyName As String
        Public Overridable ReadOnly Property FriendlyName() As String
            Get
                Return _FriendlyName
            End Get
        End Property

    End Class

    Public Class HelperBase

        Public Function StripLDAP(ByVal ldapValue As String) As String
            Dim STR_Return As String = ldapValue
            Dim LDAPQualifiers() As String = {"DC=", "DN=", "OU=", "CN="}
            For Each Qualifier As String In LDAPQualifiers
                STR_Return = STR_Return.Replace(Qualifier, String.Empty)
            Next
            STR_Return = STR_Return.Remove(STR_Return.IndexOf(","), (STR_Return.Length - STR_Return.IndexOf(",")))
            Return STR_Return
        End Function

    End Class

    Public Class DirectoryObject
        Inherits HelperBase

#Region "Properties"

#Region "Singular LDAP String Values"

        Private _cn As String
        ''' <summary>
        ''' Common Name
        ''' </summary>
        <ExtendedInfo("ReadOnly", "Common Name")> _
        Public Property cn() As String
            Get
                Return _cn
            End Get
            Set(ByVal value As String)
                _cn = value
            End Set
        End Property

        Private _distinguishedName As String
        ''' <summary>
        ''' Distinguished Name
        ''' </summary>
        ''' 
        <ExtendedInfo("ReadOnly", "Distinguished Name")> _
        Public Property distinguishedName() As String
            Get
                Return _distinguishedName
            End Get
            Set(ByVal value As String)
                _distinguishedName = value
            End Set
        End Property

        Private _objectCategory As String
        ''' <summary>
        ''' Object Category
        ''' </summary>
        ''' 
        <ExtendedInfo("ReadOnly", "Object Category")> _
        Public Property objectCategory() As String
            Get
                Return _objectCategory
            End Get
            Set(ByVal value As String)
                _objectCategory = value
            End Set
        End Property

        Private _displayName As String
        ''' <summary>
        ''' Display Name
        ''' </summary>
        <ExtendedInfo("ReadOnly", "Display Name")> _
        Public Property displayName() As String
            Get
                Return _displayName
            End Get
            Set(ByVal value As String)
                _displayName = value
            End Set
        End Property

        Private _name As String
        ''' <summary>
        ''' Name
        ''' </summary>
        <ExtendedInfo("ReadOnly", "Name")> _
        Public Property name() As String
            Get
                Return _name
            End Get
            Set(ByVal value As String)
                _name = value
            End Set
        End Property

        Private _givenName As String
        ''' <summary>
        ''' Given Name
        ''' </summary>
        <ExtendedInfo("ReadOnly", "Given Name")> _
        Public Property givenName() As String
            Get
                Return _givenName
            End Get
            Set(ByVal value As String)
                _givenName = value
            End Set
        End Property

        Private _initials As String
        ''' <summary>
        ''' Initials
        ''' </summary>
        <ExtendedInfo("ReadOnly", "Initials")> _
        Public Property initials() As String
            Get
                Return _initials
            End Get
            Set(ByVal value As String)
                _initials = value
            End Set
        End Property

        Private _sn As String
        ''' <summary>
        ''' Surname
        ''' </summary>
        <ExtendedInfo("ReadOnly", "Surname")> _
        Public Property sn() As String
            Get
                Return _sn
            End Get
            Set(ByVal value As String)
                _sn = value
            End Set
        End Property

        Private _c As String
        ''' <summary>
        ''' Country Abbreviated
        ''' </summary>
        <ExtendedInfo("ReadOnly", "Abbreviated Country")> _
        Public Property c() As String
            Get
                Return _c
            End Get
            Set(ByVal value As String)
                _c = value
            End Set
        End Property

        Private _l As String
        ''' <summary>
        ''' City/Location
        ''' </summary>
        <ExtendedInfo("Read/Write", "City/Location")> _
        Public Property l() As String
            Get
                Return _l
            End Get
            Set(ByVal value As String)
                _l = value
            End Set
        End Property

        Private _st As String
        ''' <summary>
        ''' State/Province
        ''' </summary>
        <ExtendedInfo("Read/Write", "State/Province")> _
        Public Property st() As String
            Get
                Return _st
            End Get
            Set(ByVal value As String)
                _st = value
            End Set
        End Property

        Private _title As String
        ''' <summary>
        ''' Title
        ''' </summary>
        <ExtendedInfo("Read/Write", "Title")> _
        Public Property title() As String
            Get
                Return _title
            End Get
            Set(ByVal value As String)
                _title = value
            End Set
        End Property

        Private _description As String
        ''' <summary>
        ''' Description
        ''' </summary>
        <ExtendedInfo("Read/Write", "Description")> _
        Public Property description() As String
            Get
                Return _description
            End Get
            Set(ByVal value As String)
                _description = value
            End Set
        End Property

        Private _postalCode As String
        ''' <summary>
        ''' Zip/Postal Code
        ''' </summary>
        <ExtendedInfo("Read/Write", "Zip/Postal Code")> _
        Public Property postalCode() As String
            Get
                Return _postalCode
            End Get
            Set(ByVal value As String)
                _postalCode = value
            End Set
        End Property

        Private _postOfficeBox As String
        ''' <summary>
        ''' PO Box
        ''' </summary>
        <ExtendedInfo("Read/Write", "P.O. Box")> _
        Public Property postOfficeBox() As String
            Get
                Return _postOfficeBox
            End Get
            Set(ByVal value As String)
                _postOfficeBox = value
            End Set
        End Property

        Private _physicalDeliveryOfficeName As String
        ''' <summary>
        ''' Office
        ''' </summary>
        <ExtendedInfo("Read/Write", "Office")> _
        Public Property physicalDeliveryOfficeName() As String
            Get
                Return _physicalDeliveryOfficeName
            End Get
            Set(ByVal value As String)
                _physicalDeliveryOfficeName = value
            End Set
        End Property

        Private _telephoneNumber As String
        ''' <summary>
        ''' Telephone Number
        ''' </summary>
        <ExtendedInfo("Read/Write", "Telephone Number")> _
        Public Property telephoneNumber() As String
            Get
                Return _telephoneNumber
            End Get
            Set(ByVal value As String)
                _telephoneNumber = value
            End Set
        End Property

        Private _facsimileTelephoneNumber
        ''' <summary>
        ''' Fax Number
        ''' </summary>
        <ExtendedInfo("Read/Write", "Fax Number")> _
        Public Property facsimileTelephoneNumber()
            Get
                Return _facsimileTelephoneNumber
            End Get
            Set(ByVal value)
                _facsimileTelephoneNumber = value
            End Set
        End Property

        Private _info As String
        ''' <summary>
        ''' Information
        ''' </summary>
        <ExtendedInfo("Read/Write", "Information")> _
        Public Property info() As String
            Get
                Return _info
            End Get
            Set(ByVal value As String)
                _info = value
            End Set
        End Property

        Private _co As String
        ''' <summary>
        ''' Country
        ''' </summary>
        <ExtendedInfo("ReadOnly", "Country")> _
        Public Property co() As String
            Get
                Return _co
            End Get
            Set(ByVal value As String)
                _co = value
            End Set
        End Property

        Private _department As String
        ''' <summary>
        ''' Department
        ''' </summary>
        <ExtendedInfo("Read/Write", "Department")> _
        Public Property department() As String
            Get
                Return _department
            End Get
            Set(ByVal value As String)
                _department = value
            End Set
        End Property

        Private _company As String
        ''' <summary>
        ''' Company Name
        ''' </summary>
        <ExtendedInfo("Read/Write", "Company")> _
        Public Property company() As String
            Get
                Return _company
            End Get
            Set(ByVal value As String)
                _company = value
            End Set
        End Property

        Private _streetAddress As String
        ''' <summary>
        ''' Street Address
        ''' </summary>
        <ExtendedInfo("Read/Write", "Street Address")> _
        Public Property streetAddress() As String
            Get
                Return _streetAddress
            End Get
            Set(ByVal value As String)
                _streetAddress = value
            End Set
        End Property

        Private _wWWHomePage As String
        ''' <summary>
        ''' WWW Home Page
        ''' </summary>
        <ExtendedInfo("Read/Write", "Homepage")> _
        Public Property wWWHomePage() As String
            Get
                Return _wWWHomePage
            End Get
            Set(ByVal value As String)
                _wWWHomePage = value
            End Set
        End Property

        Private _sAMAccountName As String
        ''' <summary>
        ''' Windows Login Name
        ''' </summary>
        <ExtendedInfo("ReadOnly", "Login Name")> _
        Public Property sAMAccountName() As String
            Get
                Return _sAMAccountName
            End Get
            Set(ByVal value As String)
                _sAMAccountName = value
            End Set
        End Property

        Private _userPrincipalName As String
        ''' <summary>
        ''' AD User Principal Name
        ''' </summary>
        <ExtendedInfo("ReadOnly", "User Principal Name")> _
        Public Property userPrincipalName() As String
            Get
                Return _userPrincipalName
            End Get
            Set(ByVal value As String)
                _userPrincipalName = value
            End Set
        End Property

        Private _mail As String
        ''' <summary>
        ''' Email Address
        ''' </summary>
        <ExtendedInfo("Read/Write", "Email Address")> _
        Public Property mail() As String
            Get
                Return _mail
            End Get
            Set(ByVal value As String)
                _mail = value
            End Set
        End Property

        Private _homePhone As String
        ''' <summary>
        ''' Home Phone #
        ''' </summary>
        <ExtendedInfo("Read/Write", "Home Phone Number")> _
        Public Property homePhone() As String
            Get
                Return _homePhone
            End Get
            Set(ByVal value As String)
                _homePhone = value
            End Set
        End Property

        Private _mobile As String
        ''' <summary>
        ''' Mobile Phone #
        ''' </summary>
        <ExtendedInfo("Read/Write", "Mobile Phone Number")> _
        Public Property mobile() As String
            Get
                Return _mobile
            End Get
            Set(ByVal value As String)
                _mobile = value
            End Set
        End Property

        Private _pager As String
        ''' <summary>
        ''' Pager Phone #
        ''' </summary>
        <ExtendedInfo("Read/Write", "Pager Number")> _
        Public Property pager() As String
            Get
                Return _pager
            End Get
            Set(ByVal value As String)
                _pager = value
            End Set
        End Property


#End Region

#End Region

#Region "Constructors"
        Public Sub New()
        End Sub
#End Region

    End Class

    Public Class DirectoryObjectCollection
        Inherits CollectionBase

        Public Function Add(ByVal item As DirectoryObject) As Integer
            Return MyBase.List.Add(item)
        End Function

        Public Sub Insert(ByVal index As Integer, ByVal item As DirectoryObject)
            MyBase.List.Insert(index, item)
        End Sub

        Public Sub Remove(ByVal item As DirectoryObject)
            MyBase.List.Remove(item)
        End Sub

        Public Function Contains(ByVal item As DirectoryObject) As Boolean
            Return MyBase.List.Contains(item)
        End Function

        Public Function IndexOf(ByVal item As DirectoryObject) As Integer
            Return MyBase.List.IndexOf(item)
        End Function

        Public Sub CopyTo(ByVal array As DirectoryObject(), ByVal index As Integer)
            MyBase.List.CopyTo(array, index)
        End Sub

        Default Public Property Item(ByVal index As Integer) As DirectoryObject
            Get
                Return DirectCast(MyBase.List(index), DirectoryObject)
            End Get
            Set(ByVal value As DirectoryObject)
                MyBase.List(index) = value
            End Set
        End Property

        Default Public ReadOnly Property Item(ByVal cn As String) As DirectoryObject
            Get
                Dim d As DirectoryObject = Nothing
                For Each Obj As DirectoryObject In MyBase.List
                    If Obj.cn = cn Then
                        d = Obj
                    End If
                Next
                Return d
            End Get
        End Property

    End Class

    Public Class CategoryImage
        Private _CategoryName As String
        Public Property CategoryName() As String
            Get
                Return _CategoryName
            End Get
            Set(ByVal value As String)
                _CategoryName = value
            End Set
        End Property

        Private _ImageURL As String
        Public Property ImageURL() As String
            Get
                Return _ImageURL
            End Get
            Set(ByVal value As String)
                _ImageURL = value
            End Set
        End Property

        Public Sub New()

        End Sub

        Public Sub New(ByVal _categoryname_ As String, ByVal _imageurl_ As String)
            CategoryName = _categoryname_
            ImageURL = _imageurl_
        End Sub
    End Class

    Public Class CategoryImageCollection
        Inherits CollectionBase

        Public Function Add(ByVal item As CategoryImage) As Integer
            Return MyBase.List.Add(item)
        End Function

        Public Function add(ByVal _categoryname_ As String, ByVal _imageurl_ As String)
            Return MyBase.List.Add(New CategoryImage(_categoryname_, _imageurl_))
        End Function

        Public Sub Insert(ByVal index As Integer, ByVal item As CategoryImage)
            MyBase.List.Insert(index, item)
        End Sub

        Public Sub Remove(ByVal item As CategoryImage)
            MyBase.List.Remove(item)
        End Sub

        Public Function Contains(ByVal item As CategoryImage) As Boolean
            Return MyBase.List.Contains(item)
        End Function

        Public Function IndexOf(ByVal item As DirectoryObject) As Integer
            Return MyBase.List.IndexOf(item)
        End Function

        Public Sub CopyTo(ByVal array As CategoryImage(), ByVal index As Integer)
            MyBase.List.CopyTo(array, index)
        End Sub

        Default Public Property Item(ByVal index As Integer) As CategoryImage
            Get
                Return DirectCast(MyBase.List(index), CategoryImage)
            End Get
            Set(ByVal value As CategoryImage)
                MyBase.List(index) = value
            End Set
        End Property

        Default Public ReadOnly Property Item(ByVal name As String) As CategoryImage
            Get
                Dim c As CategoryImage = Nothing
                For Each cat As CategoryImage In MyBase.List
                    If cat.CategoryName = name Then
                        c = cat
                    End If
                Next
                Return c
            End Get
        End Property

        Public Sub New()
            Dim WebConfigImageURLs As ImageURLStringsCollection = ADHelperConfig.GetImageURLs()
            If Not (WebConfigImageURLs Is Nothing) Then
                For Each Item As ImageURLStrings In WebConfigImageURLs
                    Me.Add(Item.Categoryname, Item.URL)
                Next
            End If
        End Sub

    End Class

    Public Class LDAPConfiguration
        Private _HostName
        ''' <summary>
        ''' DNS HostName or IP Address of Active Directory Server
        ''' </summary>
        Public Property HostName() As String
            Get
                Return _HostName
            End Get
            Set(ByVal value As String)
                _HostName = value
            End Set
        End Property

        Private _RootDN
        ''' <summary>
        ''' Active Directory Search Root (ex "DC=Domain,DC=Local")
        ''' </summary>
        Public Property RootDN() As String
            Get
                Return _RootDN
            End Get
            Set(ByVal value As String)
                _RootDN = value
            End Set
        End Property

        Private _UserName As String
        ''' <summary>
        ''' Distinguished Name of Active Directory User for LDAP Authentication (ex "CN=Administrator,CN=Users,DC=Domain,DC=Local")
        ''' </summary>
        Public Property UserName() As String
            Get
                Return _UserName
            End Get
            Set(ByVal value As String)
                _UserName = value
            End Set
        End Property

        Private _Password As String
        ''' <summary>
        ''' Active Directory User Password
        ''' </summary>
        Public Property Password() As String
            Get
                Return _Password
            End Get
            Set(ByVal value As String)
                _Password = value
            End Set
        End Property

        Private _Port As String = "389"
        ''' <summary>
        ''' LDAP Port (Default 389)
        ''' </summary>
        Public Property Port() As String
            Get
                Return _Port
            End Get
            Set(ByVal value As String)
                _Port = value
            End Set
        End Property

        Protected Friend ReadOnly Property ConnectionString() As String
            Get
                Return "LDAP://" + HostName + ":" + Port + "/" + RootDN
            End Get
        End Property

        Protected Friend ReadOnly Property DNConnectionString()
            Get
                Return "LDAP://" + HostName + ":" + Port + "/{0}"
            End Get
        End Property

        Public Sub New()
            Dim webConfig As ADHelperConfig = ADHelperConfig.GetConfig()
            If Not (webConfig.AD_Host Is Nothing) Then HostName = webConfig.AD_Host
            If Not (webConfig.AD_Root Is Nothing) Then RootDN = webConfig.AD_Root
            If Not (webConfig.AD_User Is Nothing) Then UserName = webConfig.AD_User
            If Not (webConfig.AD_Password Is Nothing) Then Password = webConfig.AD_Password
        End Sub

        Public Sub New(ByVal host As String, ByVal root As String)
            HostName = host
            RootDN = root
        End Sub

        Public Sub New(ByVal host As String, ByVal root As String, ByVal user As String, ByVal pass As String)
            HostName = host
            RootDN = root
            UserName = user
            Password = pass
        End Sub

    End Class

    <System.ComponentModel.DataObject()> _
    Public NotInheritable Class HelperFunctions
        Inherits HelperBase

        ''' <summary>
        ''' ADHelper.DirectoryHelper.LDAPConfiguration representing the connection to the Active Directory
        ''' </summary>
        Private LDAPConfig As LDAPConfiguration
        ''' <summary>
        ''' AdHelper.DirectoryHelper.CategoryImageCollection representing Category-&gt;Image assignments used in the ObjectDataTable
        ''' </summary>
        Public ImageURLs As CategoryImageCollection

#Region "Constructors"
        ''' <summary>
        ''' Default Constructor.  Attempts to load configuration values from web.config, otherwise uses machine domain as root, and interactive user as authentication.
        ''' </summary>
        Public Sub New()
            LDAPConfig = New LDAPConfiguration
            ImageURLs = New CategoryImageCollection
        End Sub

        ''' <param name="Settings">ADHelper.DirectoryHelper.LDAPConfiguration Object representing the LDAP Configuration parameters to use.</param>
        Public Sub New(ByVal Settings As LDAPConfiguration)
            LDAPConfig = Settings
            ImageURLs = New CategoryImageCollection
        End Sub

        ''' <summary>
        ''' Creates a new Helperfunction Class, using the specified Host and Root, but using the interactive user's security to access the Active Directory
        ''' </summary>
        ''' <param name="host">Active Directory LDAP Host Address (Hostname or IP Address)</param>
        ''' <param name="root">Active Directory LDAP Root (ex: "DC=Domain,DC=Local")</param>
        Public Sub New(ByVal host As String, ByVal root As String)
            LDAPConfig = New LDAPConfiguration(host, root)
            ImageURLs = New CategoryImageCollection
        End Sub

        ''' <param name="host">Active Directory LDAP Host Address (Hostname or IP Address)</param>
        ''' <param name="root">Active Directory LDAP Root (ex: "DC=Domain,DC=Local")</param>
        ''' <param name="user">Distinguished Name of Active Directory user for authentication (ex: "CN=Administrator,CN=Users,DC=Domain,DC=Local")</param>
        ''' <param name="pass">Active Directory user's password for Authentication</param>
        Public Sub New(ByVal host As String, ByVal root As String, ByVal user As String, ByVal pass As String)
            LDAPConfig = New LDAPConfiguration(host, root, user, pass)
            ImageURLs = New CategoryImageCollection
        End Sub

#End Region

        ''' <summary>
        ''' Returns an ADHelper.ObjectData.ObjectDatatable of all Active Directory objects from the Root specified in the LDAP Configuration.
        ''' </summary>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Select)> _
        Public Function GetObjectTree() As ObjectData.ObjectTreeDataTable
            Dim ObjectTree As New ObjectData.ObjectTreeDataTable
            Using AD As New System.DirectoryServices.DirectoryEntry(LDAPConfig.ConnectionString, LDAPConfig.UserName, LDAPConfig.Password)
                AD.AuthenticationType = DirectoryServices.AuthenticationTypes.None

                Using Search As New DirectoryServices.DirectorySearcher(AD)
                    Search.Filter = My.Resources.SearchFilter
                    Search.Sort.Direction = DirectoryServices.SortDirection.Ascending
                    Search.Sort.PropertyName = "name"
                    Search.PropertiesToLoad.Add("distinguishedName")
                    Search.PropertiesToLoad.Add("name")
                    Search.PropertiesToLoad.Add("objectCategory")

                    Dim results As DirectoryServices.SearchResultCollection = Search.FindAll()
                    If Not IsNothing(results) AndAlso results.Count > 0 Then
                        For Each result As System.DirectoryServices.SearchResult In results
                            Dim objectRow As ObjectData.ObjectTreeRow = ObjectTree.NewRow()
                            objectRow.distinguishedName = ExtractPropertyValue("distinguishedName", result)
                            If Not String.Equals(objectRow.distinguishedName, LDAPConfig.RootDN, StringComparison.CurrentCultureIgnoreCase) Then
                                objectRow.parentName = objectRow.distinguishedName.Remove(0, (objectRow.distinguishedName.IndexOf(",") + 1))
                            End If
                            objectRow.name = ExtractPropertyValue("name", result)
                            objectRow.objectCategory = ExtractPropertyValue("objectCategory", result)
                            objectRow.objectCategoryUnTagged = StripLDAP(objectRow.objectCategory)
                            objectRow.CategoryImageURL = SetImageURL(objectRow.objectCategoryUnTagged)
                            ObjectTree.Rows.Add(objectRow)
                        Next
                    End If

                End Using 'Search
            End Using 'AD

            Return ObjectTree
        End Function

        ''' <summary>
        ''' Returns a Dataview (Based on the ObjectDataTable) of all users in the Active Directory
        ''' </summary>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Select)> _
        Public Function GetUserList() As DataView
            Dim UserList As DataView = Nothing
            Dim ObjectTree As ObjectData.ObjectTreeDataTable = GetObjectTree()
            ObjectTree.DefaultView.RowFilter = "objectCategoryUnTagged = 'Person'"
            UserList = ObjectTree.DefaultView
            Return UserList
        End Function

        ''' <summary>
        ''' Returns a Strongly define object populated with the LDAP properties of any specified Active Directory Object.
        ''' </summary>
        ''' <param name="dn">The Distinguished Name of the object to be retrieved.</param>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Select)> _
        Public Function GetObject(ByVal dn As String) As DirectoryObject
            Dim objData As New DirectoryObject
            If Not String.IsNullOrEmpty(dn) Then
                Dim FilterDN As String = String.Format(LDAPConfig.DNConnectionString, dn)

                Using ADObject As New System.DirectoryServices.DirectoryEntry(FilterDN, LDAPConfig.UserName, LDAPConfig.Password)
                    ADObject.AuthenticationType = DirectoryServices.AuthenticationTypes.None
                    ADObject.RefreshCache()

                    If Not (ADObject Is Nothing) Then
                        Dim Properties As Type = GetType(DirectoryObject)
                        For Each Prop As System.Reflection.PropertyInfo In Properties.GetProperties()
                            SetPropertyValue(objData, Prop, ADObject)
                        Next
                    End If
                End Using
            End If

            Return objData
        End Function

        ''' <summary>
        ''' Returns a DirectoryObject Collection representing all available Active Directory Groups
        ''' </summary>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Select)> _
        Public Function GetGroups() As DirectoryObjectCollection
            Dim Groups As New DirectoryObjectCollection
            Using AD As New DirectoryServices.DirectoryEntry(LDAPConfig.ConnectionString, LDAPConfig.UserName, LDAPConfig.Password)
                AD.AuthenticationType = DirectoryServices.AuthenticationTypes.None
                Using Search As New DirectoryServices.DirectorySearcher
                    Search.Filter = My.Resources.FilterGroups
                    Search.Sort.PropertyName = "cn"
                    Search.Sort.Direction = DirectoryServices.SortDirection.Ascending
                    Dim SearchResults As DirectoryServices.SearchResultCollection = Search.FindAll()
                    If Not IsNothing(SearchResults) AndAlso SearchResults.Count > 0 Then
                        Dim Properties As Type = GetType(DirectoryObject)
                        For Each result As DirectoryServices.SearchResult In SearchResults
                            Dim GroupObj As New DirectoryObject
                            For Each Prop As System.Reflection.PropertyInfo In Properties.GetProperties()
                                Prop.SetValue(GroupObj, ExtractPropertyValue(Prop.Name, result), Nothing)
                            Next
                            Groups.Add(GroupObj)
                        Next
                    End If
                End Using 'SearchResults
            End Using 'AD
            Return Groups
        End Function

        ''' <summary>
        ''' Returns a DirectoryObject Collection representing all groups to which the specified user is a member of.
        ''' </summary>
        ''' <param name="dn">The Distinguished name of the User to retrieve the Memberships for.</param>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Select)> _
        Public Function GetGroupsForUser(ByVal dn As String) As DirectoryObjectCollection
            Dim groups As New DirectoryObjectCollection
            If Not String.IsNullOrEmpty(dn) Then
                Using AD As New DirectoryServices.DirectoryEntry(LDAPConfig.ConnectionString, LDAPConfig.UserName, LDAPConfig.Password)
                    AD.AuthenticationType = DirectoryServices.AuthenticationTypes.None
                    Using Search As New DirectoryServices.DirectorySearcher
                        Search.Filter = String.Format(My.Resources.FilterGroupsForUser, dn)
                        Search.Sort.PropertyName = "cn"
                        Search.Sort.Direction = DirectoryServices.SortDirection.Ascending
                        Dim SearchResults As DirectoryServices.SearchResultCollection = Search.FindAll()
                        If Not IsNothing(SearchResults) AndAlso SearchResults.Count > 0 Then
                            Dim Properties As Type = GetType(DirectoryObject)
                            For Each result As DirectoryServices.SearchResult In SearchResults
                                Dim GroupObj As New DirectoryObject
                                For Each Prop As System.Reflection.PropertyInfo In Properties.GetProperties()
                                    Prop.SetValue(GroupObj, ExtractPropertyValue(Prop.Name, result), Nothing)
                                Next
                                groups.Add(GroupObj)
                            Next
                        End If
                    End Using 'SearchResults
                End Using 'AD
            End If

            Return groups
        End Function

        ''' <summary>
        ''' Returns a DirectoryObject Collection representing the users belonging to the specified group.
        ''' </summary>
        ''' <param name="dn">The Distinguished Name of the Group</param>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Select)> _
        Public Function GetUsersForGroup(ByVal dn As String) As DirectoryObjectCollection
            Dim GroupMembers As New DirectoryObjectCollection
            If Not String.IsNullOrEmpty(dn) Then
                Using AD As New DirectoryServices.DirectoryEntry(LDAPConfig.ConnectionString, LDAPConfig.UserName, LDAPConfig.Password)
                    AD.AuthenticationType = DirectoryServices.AuthenticationTypes.None
                    Using Search As New DirectoryServices.DirectorySearcher
                        Search.Filter = String.Format(My.Resources.FilterUsersForGroup, dn)
                        Search.Sort.PropertyName = "cn"
                        Search.Sort.Direction = DirectoryServices.SortDirection.Ascending
                        Dim SearchResults As DirectoryServices.SearchResultCollection = Search.FindAll()
                        If Not IsNothing(SearchResults) AndAlso SearchResults.Count > 0 Then
                            Dim Properties As Type = GetType(DirectoryObject)
                            For Each result As DirectoryServices.SearchResult In SearchResults
                                Dim UserObj As New DirectoryObject
                                For Each Prop As System.Reflection.PropertyInfo In Properties.GetProperties()
                                    Prop.SetValue(UserObj, ExtractPropertyValue(Prop.Name, result), Nothing)
                                Next 'Prop
                                GroupMembers.Add(UserObj)
                            Next 'result
                        End If
                    End Using 'Search
                End Using 'AD
            End If
            Return GroupMembers
        End Function

        ''' <summary>
        ''' Depreciated - Please use GetUsersForGroup() - Returns a DirectoryObject Collection representing the users belonging to the specified group
        ''' </summary>
        ''' <param name="dn">The Distinguished Name of the Group to retrieve Users from.</param>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Select)> _
        Public Function GetGroupMembership(ByVal dn As String) As DirectoryObjectCollection
            Return GetUsersForGroup(dn)
        End Function

        ''' <summary>
        ''' Updates a particular object in the Active Directory from the values specified.
        ''' </summary>
        ''' <param name="newValues">Full representation of the Object being modified, with the new property values set.</param>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Update)> _
        Public Function UpdateObject(ByVal newValues As DirectoryObject) As Integer
            Dim OldObject As DirectoryObject = GetObject(newValues.distinguishedName)
            Dim Properties As Type = GetType(DirectoryObject)

            Dim FilterDN As String = String.Format(LDAPConfig.DNConnectionString, OldObject.distinguishedName)
            Using ADObject As New System.DirectoryServices.DirectoryEntry(FilterDN, LDAPConfig.UserName, LDAPConfig.Password)
                ADObject.RefreshCache()
                For Each Prop As System.Reflection.PropertyInfo In Properties.GetProperties()
                    Dim ExtendedInfo As ExtendedInfoAttribute = DirectCast(Attribute.GetCustomAttribute(Prop, GetType(ExtendedInfoAttribute), True), ExtendedInfoAttribute)
                    If ExtendedInfo.AccessMode = "Read/Write" Then
                        If String.IsNullOrEmpty(Prop.GetValue(newValues, Nothing).ToString) Then Prop.SetValue(newValues, Nothing, Nothing)
                        If Not (String.Equals(Prop.GetValue(OldObject, Nothing), Prop.GetValue(newValues, Nothing))) Then
                            If (Prop.GetValue(newValues, Nothing) Is Nothing) And ADObject.Properties.Contains(Prop.Name) Then
                                ADObject.Properties(Prop.Name).Remove(Prop.GetValue(OldObject, Nothing).ToString)
                            ElseIf (Not (Prop.GetValue(newValues, Nothing) Is Nothing)) And (Not (ADObject.Properties.Contains(Prop.Name))) Then
                                ADObject.Properties(Prop.Name).Add(Prop.GetValue(newValues, Nothing).ToString)
                            ElseIf (Not (Prop.GetValue(newValues, Nothing) Is Nothing)) And (ADObject.Properties.Contains(Prop.Name)) Then
                                ADObject.Properties(Prop.Name).Item(0) = Prop.GetValue(newValues, Nothing).ToString
                            End If
                        End If ' String.equals
                    End If ' ExtendedInfo.Accessmode
                Next
                ADObject.CommitChanges()
            End Using
        End Function

        ''' <summary>
        ''' Adds the user specified to the group specified.
        ''' </summary>
        ''' <param name="userDN">Distinguished name of the User</param>
        ''' <param name="groupDN">Distinguished name of the Group</param>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Insert)> _
        Public Function AddUserToGroup(ByVal userDN As String, ByVal groupDN As String) As Boolean
            If (Not (String.IsNullOrEmpty(userDN))) And (Not (String.IsNullOrEmpty(groupDN))) Then
                Dim FilterDN As String = String.Format(LDAPConfig.DNConnectionString, groupDN)
                Using ADObject As New System.DirectoryServices.DirectoryEntry(FilterDN, LDAPConfig.UserName, LDAPConfig.Password)
                    ADObject.AuthenticationType = DirectoryServices.AuthenticationTypes.None
                    ADObject.RefreshCache()
                    If Not (ADObject Is Nothing) Then
                        If (ADObject.Properties.Contains("member")) Then
                            If (ADObject.Properties("member").IndexOf(userDN) = -1) Then
                                ADObject.Properties("member").Add(userDN)
                            End If
                        Else
                            ADObject.Properties("member").Add(userDN)
                        End If
                        ADObject.CommitChanges()
                    End If
                End Using
            End If
        End Function

        ''' <summary>
        ''' Removes the user specified from the group specified.
        ''' </summary>
        ''' <param name="userDN">Distinguished Name of the user</param>
        ''' <param name="groupDN">Distinguished Name of the group</param>
        <System.ComponentModel.DataObjectMethod(ComponentModel.DataObjectMethodType.Delete)> _
        Public Function RemoveUserFromGroup(ByVal userDN As String, ByVal groupDN As String) As Boolean
            If (Not (String.IsNullOrEmpty(userDN))) And (Not (String.IsNullOrEmpty(groupDN))) Then
                Dim FilterDN As String = String.Format(LDAPConfig.DNConnectionString, groupDN)
                Using ADObject As New System.DirectoryServices.DirectoryEntry(FilterDN, LDAPConfig.UserName, LDAPConfig.Password)
                    ADObject.AuthenticationType = DirectoryServices.AuthenticationTypes.None
                    ADObject.RefreshCache()
                    If Not (ADObject Is Nothing) Then
                        If (ADObject.Properties.Contains("member")) Then
                            Dim idx As Integer = ADObject.Properties("member").IndexOf(userDN)
                            ADObject.Properties("member").RemoveAt(idx)
                        End If
                        ADObject.CommitChanges()
                    End If
                End Using
            End If
        End Function

        ''' <summary>
        ''' Forces a password reset on a particular user.
        ''' </summary>
        ''' <param name="UserDN">Distinguished name of the user to reset the password for.</param>
        ''' <param name="NewPassword">The new password to set.</param>
        Public Function ResetPassword(ByVal UserDN As String, ByVal NewPassword As String) As Boolean
            Dim FilterDN As String = String.Format(LDAPConfig.DNConnectionString, UserDN)
            Using ADObject As New System.DirectoryServices.DirectoryEntry(FilterDN, LDAPConfig.UserName, LDAPConfig.Password)
                If Not (ADObject Is Nothing) Then
                    Try
                        ADObject.Invoke("SetPassword", NewPassword)
                        ADObject.CommitChanges()
                    Catch ex As DirectoryServices.DirectoryServicesCOMException
                        Return False
                    End Try
                End If
            End Using
            Return True
        End Function

        Private Sub SetPropertyValue(ByRef DirObj As DirectoryObject, ByVal p As System.Reflection.PropertyInfo, ByRef result As System.DirectoryServices.DirectoryEntry)
            If p.CanWrite Then
                If result.Properties.Contains(p.Name) Then
                    If result.Properties.Item(p.Name).Count > 1 Then
                        Dim val As IEnumerator = result.Properties(p.Name).GetEnumerator
                        While val.MoveNext
                            p.SetValue(DirObj, val.Current, Nothing)
                        End While
                    Else
                        p.SetValue(DirObj, result.Properties(p.Name).Item(0).ToString, Nothing)
                    End If
                End If
            End If
        End Sub

        Private Shared Function ExtractPropertyValue(ByVal propertyName As String, ByVal result As System.DirectoryServices.SearchResult) As String
            Dim PropertyValue As String = String.Empty
            If result.Properties.Contains(propertyName) Then
                PropertyValue = result.Properties(propertyName).Item(0).ToString
            End If
            Return PropertyValue
        End Function

        Private Function SetImageURL(ByVal category As String) As String
            Dim ImageURL As String = String.Empty
            Dim cat As CategoryImage = ImageURLs.Item(category)
            If Not (cat Is Nothing) Then ImageURL = cat.ImageURL
            If (String.IsNullOrEmpty(ImageURL)) Then
                Dim DefaultIMG As CategoryImage = ImageURLs("Default")
                If Not (DefaultIMG Is Nothing) Then ImageURL = DefaultIMG.ImageURL
            End If
            Return ImageURL
        End Function

    End Class

End Namespace